package facades;

import java.util.ArrayList;
import java.util.Collection;

import common.Result;


import hitExceptions.InvalidDataException;
import hitExceptions.InvalidOperationException;
import coreModel.HIT;
import coreModel.Product;
import coreModel.ProductContainer;
import coreModel.ProductManager;
import coreModel.StorageUnit;
import dataPersistence.TransactionManager;
import dataPersistence.dao.IProductDAO;
import dataPersistence.dao.Product_DB_DAO;
import dataPersistence.dto.ProductDTO;


/** A class to be a middleman between the gui and the core model. It represents  
 * much of the functionality of the Products of the model to the gui. It 
 * accepts core model objects as parameters, calls functions in the core model, 
 * and notifies its observers in the gui.
 */
public class ProductFacade extends java.util.Observable
{
	final static NotificationEnum defaultN = NotificationEnum.PRODUCT_CHANGED; 
	private static ProductFacade instance;


	/**
	 * Constructor, made private because this class is a singleton
	 */
	private ProductFacade()
	{
		//empty
	}

	/**
	 * Returns the instance of the productFacade
	 * @return instance of the product facade
	 */
	public static ProductFacade getInstance()
	{
		if(instance==null)
		{
			instance= new ProductFacade();
		}
		return instance;
	}

	/** Requests that a product be added to the model in a specified container.
	 * 
	 * @param newProduct An object to contain the data for the new product.
	 * @param container The container into which to put the new product.
	 * @throws InvalidOperationException 
	 */
	public void addProduct(Product newProduct, ProductContainer container)
	{

		//begin transaction
		Result result = new Result();
		result =TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());

		ProductManager pMgr = HIT.getInstance().getProductManager();

		try
		{
			result=pMgr.addProductToContainer(newProduct, container);
			if(!result.getStatus()) System.err.println(result.getMessage());
		}
		catch (Exception e)
		{
			result.setStatus(false);
			e.printStackTrace();
			if(!HIT.getInstance().isLoadingFromDataBase())
				TransactionManager.end(result.getStatus());
			return;
		}

		if(!HIT.getInstance().isLoadingFromDataBase())
			//update db or roll back
			TransactionManager.end(result.getStatus());


		//notify controller
		this.setChanged();
		notifyObservers(new Notification(newProduct,defaultN,NotificationEnum.ADD));
		this.clearChanged();
	}

	/** Requests that a product in the model be edited.
	 * 
	 * @param modifiedProduct An object to contain the new data for the product.
	 * @param newProduct 
	 * @throws InvalidDataException 
	 */
	public void editProduct(Product originalProduct, Product newProduct) throws InvalidDataException
	{
		Result result = new Result();

		//begin transaction
		result=TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());

		originalProduct.setDescription(newProduct.getDescription());
		originalProduct.setShelfLife(newProduct.getShelfLife());
		originalProduct.setSupply(newProduct.getSupply());
		originalProduct.setThreeMonthSupply(newProduct.getThreeMonthSupply());

		//Creating DTO
		ProductDTO tempDTO = new ProductDTO(originalProduct);

		//Create DAO
		IProductDAO dao = HIT.getInstance().getFactory().getProductDAO();

		//update DB
		result=dao.update(tempDTO);
		if(!result.getStatus()) System.err.println(result.getMessage());


		TransactionManager.end(result.getStatus());


		//notify controller
		this.setChanged();
		notifyObservers(new Notification(originalProduct, defaultN,NotificationEnum.EDIT));
		this.clearChanged();
	}



	/** Requests that a product be moved from a specified container to a 
	 * specified container in the model.
	 * 
	 * @param product An object containing the data to look for and move.
	 * @param oldContainer The container from which to move the product.
	 * @param newContainer The container into which to move the product.
	 * @throws InvalidOperationException 
	 */
	public void moveProduct(Product product, ProductContainer oldContainer, 
			ProductContainer newContainer) throws InvalidOperationException
			{

		Result result = new Result();
		//begin transaction
		result=TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());

		//create DAO
		IProductDAO dao = HIT.getInstance().getFactory().getProductDAO();

		//create DTO
		ProductDTO dto = new ProductDTO(product);

		//move Product in database
		result=dao.updateProductInContainer(dto, oldContainer.getContainerID().getValue(), 
				newContainer.getContainerID().getValue());

		if(!result.getStatus()) System.err.println(result.getMessage());

		HIT.getInstance().getProductManager().moveProduct(product,oldContainer,newContainer);

		//close transaction
		TransactionManager.end(result.getStatus());



		this.setChanged();
		notifyObservers(new Notification(product,defaultN,NotificationEnum.TRANSFER));
		this.clearChanged();
			}

	public void removeProductFromContainer(Product addedProduct, 
			ProductContainer target) throws InvalidOperationException 
			{
		Result result = new Result();
		//begin transaction
		result =TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());

		//create dto
		ProductDTO dto = new ProductDTO(addedProduct);

		//create dao
		IProductDAO dao = HIT.getInstance().getFactory().getProductDAO();

		//execute remove
		result =dao.removeProductFromContainer(dto, target.getContainerID().getValue());
		if(!result.getStatus()) System.err.println(result.getMessage());

		//close transaction
		TransactionManager.end(result.getStatus());

		HIT.getInstance().getProductManager().removeProductFromContainer(addedProduct,target);
		this.setChanged();
		notifyObservers(new Notification(addedProduct, defaultN,NotificationEnum.REMOVE));
		this.clearChanged();
			}


	public void removeProductFromSystem(Product product) throws InvalidOperationException
	{
		//TODO need to add code for this
		TransactionManager.begin();
		Result result = new Result();

		ProductDTO dto = new ProductDTO(product);

		IProductDAO dao = HIT.getInstance().getFactory().getProductDAO();

		//delete all instances of this product
		result =dao.removeProductFromSystem(dto);


		TransactionManager.end(result.getStatus());

		//remove from all list
		HIT.getInstance().getProductManager().removeProductFromAllList(product);
		//may need to notify removed list ***********

		//get container list
		Collection<ProductContainer> containers= HIT.getInstance().
		getProductManager().getContainersForProduct(product);

		//Making a copy because the other list is unmodifable 
		Collection<ProductContainer> cpContainers = new ArrayList<ProductContainer>();
		cpContainers.addAll(containers);

		//removing from each container the product
		for(ProductContainer container: cpContainers)
		{
			HIT.getInstance().getProductManager().removeProductFromContainer(product, container);
			HIT.getInstance().getItemManager().removeProductFromContainer(product,container);
			HIT.getInstance().getItemManager().removeProductFromProductlist(product);
		}

		//notify
		this.setChanged();
		notifyObservers(new Notification (product,defaultN,NotificationEnum.REMOVE));
		this.clearChanged();


	}

	public void removeProductsFromContainer(ArrayList<Product> productList,
			StorageUnit target) throws InvalidOperationException 
			{
		Result result = new Result();

		//begin Transaction
		result =TransactionManager.begin();
		if(!result.getStatus()) System.err.println(result.getMessage());

		//create dao
		IProductDAO dao = HIT.getInstance().getFactory().getProductDAO();

		for (Product p : productList)
		{
			//create dto
			ProductDTO dto = new ProductDTO();

			// execute remove
			result =dao.removeProductFromContainer(dto, target.getContainerID().getValue());
			if(!result.getStatus()) 
			{
				System.err.println(result.getMessage());
				break;
			}

			HIT.getInstance().getProductManager().removeProductFromContainer(p,target);
		}

		//end transaction
		TransactionManager.end(result.getStatus());

		this.setChanged();
		notifyObservers(new Notification(productList, defaultN, NotificationEnum.REMOVE));
		this.clearChanged();
			}


}
